#coding:utf-8
#version: 1.0-20240710
import re, time, datetime
import os, os.path, pathlib
from ftplib import FTP, error_perm
from configparser import ConfigParser

class SyncFile():

    dir_pattern = re.compile(r'[a-zA-Z]{3}\s+\d{2}\s+[\d:]{4,}\s(.*)$')
    file_pattern = re.compile(r'(\d+)\s+[a-zA-Z]{3}\s+\d{2}\s+[\d:]{4,}\s(.*)$')
    def connect_by_ini(self, ini_section):
        conf = ConfigParser()
        conf.read(pathlib.Path.home() / 'ftp.ini')
        self.connect(conf[ini_section]['host'], int(conf[ini_section]['port']),
            conf[ini_section]['username'], conf[ini_section]['pwd_or_key'])

    def connect(self, host, port, username, pwd_or_key):
        self.__ftp = FTP()
        self.__ftp.encoding = 'UTF-8'  # 设置编码
        try:
            self.__ftp.connect(host, port)
            print("Connecting to server:", host, port)
            self.__ftp.login(user=username, passwd=pwd_or_key)
            print("Successfully logged in.")
        except Exception as e:
            print(f"Login failed: {e}")

    def __get_ftp_dirs(self, *args):
        '''拷贝了 nlst() 和 dir() 代码修改，返回详细信息而不打印'''
        cmd = 'LIST'
        for arg in args:
            cmd = cmd + (' ' + arg)
        dirs = []
        files = []

        def filter(file):
            #print(file)
            if file.startswith('d'):
                file = self.dir_pattern.findall(file)[0]
                dirs.append(file)
            elif file.startswith('-'):
                # r'(\d+)\s+[a-zA-Z]{3}\s+\d{2}\s+[\d:]{4,}\s(.*)$'
                match_file = self.file_pattern.findall(file)[0]
                files.append(match_file)

        self.__ftp.retrlines(cmd, filter)
        return dirs, files
    
    def __list_local_files(self, dirpath):
        dirs = []
        files = []
        for filename in os.listdir(dirpath):
            path = dirpath + '/' + filename
            if os.path.isfile(path):
                files.append(filename)
            else:
                dirs.append(filename)
        return dirs, files

    def __sync_ftp_files(self, curr_ftp_path):
        r_dirs, r_file_tuples = self.__get_ftp_dirs(curr_ftp_path)
        r_files = [x[1] for x in r_file_tuples]
        curr_local_dir = curr_ftp_path.replace(self.__ftp_root_path, self.__local_root_path)
        l_dirs, l_files = self.__list_local_files(curr_local_dir)

        print('\n----------------' + curr_ftp_path + '----------------')
        # 上传 本地新增的文件
        local_add_files = list(set(l_files).difference(set(r_files)))
        for relative_file_path in local_add_files:
            self.__uploda_file(relative_file_path, curr_ftp_path, curr_local_dir)

        # 删除 ftp多出的文件
        ftp_add_files = list(set(r_files).difference(set(l_files)))
        for relative_file_path in ftp_add_files:
            remote_file = curr_ftp_path+'/' + relative_file_path
            print ("删除FTP文件:"+remote_file)
            self.__ftp.delete(remote_file)
        self.__ftp.set_debuglevel(0)

        # 上传 本地最近修改的文件
        cmp_files = list(set(l_files).intersection(set(r_files)))
        for relative_file_path in cmp_files:
            local_is_newer = self.__local_is_newer(relative_file_path, r_file_tuples, curr_ftp_path, curr_local_dir)
            if local_is_newer=="YES":
                self.__uploda_file(relative_file_path, curr_ftp_path, curr_local_dir)
            elif local_is_newer=="NO":
                self.__download_file(relative_file_path, curr_ftp_path, curr_local_dir)

        # 上传 本地多余的目录
        local_add_dirs = list(set(l_dirs).difference(set(r_dirs)))
        for d in local_add_dirs:
            ftp_add_dir = curr_ftp_path+'/'+ d
            print('创建FTP目录：' + ftp_add_dir)
            self.__ftp.mkd(ftp_add_dir)
            # 上传 本地多余的文件
            self.__sync_ftp_files(ftp_add_dir)

        # 删除 ftp多出的目录
        ftp_add_dirs = list(set(r_dirs).difference(set(l_dirs)))
        for d in ftp_add_dirs:
            ftp_add_dir = curr_ftp_path+'/'+ d
            self.__del_dir(ftp_add_dir)

        # 检查已经存在ftp是否和本地相同
        inter_dirs = set(r_dirs)&set(l_dirs)
        for dn in inter_dirs:
            self.__sync_ftp_files(curr_ftp_path + '/' + dn)

    def __download_file(self, file_path, curr_ftp_path, curr_local_dir):
        fp = open(curr_local_dir + '/' + file_path, 'wb')
        bufsize = 1024
        remote_file = curr_ftp_path+'/' + file_path
        print('>>>下载：' + remote_file)
        self.__ftp.retrbinary('RETR ' + remote_file, fp.write, bufsize)

    def __uploda_file(self, relative_file_path, curr_ftp_path, curr_local_dir):
        local_file, remote_file = curr_local_dir + '/' + relative_file_path,  curr_ftp_path + '/' + relative_file_path
        try:
            print("\t上传", local_file, '-->', remote_file)
            with open(local_file, 'rb') as file :
                self.__ftp.storbinary('STOR %s' % remote_file, file)
        except Exception as err:
            print(err)

    def __local_is_newer(self, file_path, r_file_tuples, curr_ftp_path, curr_local_path, compare_time=False):
        remote_file = curr_ftp_path + '/' + file_path
        size_r = int([x[0] for x in r_file_tuples if x[1]==file_path][0])

        local_file = curr_local_path + '/' + file_path
        size_l = os.path.getsize(local_file)

        if not compare_time:
            return "YES" if size_r!=size_l else "NO-OP"

        if size_r!=size_l:
            mtime_r = self.__ftp.sendcmd("MDTM "+remote_file)[4:].strip()
            mtime_r = datetime.datetime.strptime(mtime_r, '%Y%m%d%H%M%S')
            tz_offset = -1*time.timezone if (time.localtime().tm_isdst == 0) else time.altzone
            mtime_r = mtime_r.timestamp() + tz_offset
            mtime_l = os.path.getmtime(local_file)

            if mtime_l > mtime_r:
                print("本地新：", file_path, size_l, mtime_l, ">", size_r, mtime_r)
                return "YES"
            else:
                print("远程新：", file_path, size_l, mtime_l, "<", size_r, mtime_r)
                return "NO"
        else:
            return "NO-OP"

    def __del_dir(self, curr_ftp_path):
        print("开始进入目录："+curr_ftp_path)
        r_dirs, r_file_tuples = self.__get_ftp_dirs(curr_ftp_path)
        r_files = [x[1] for x in r_file_tuples]

        for fn in r_files:
            print("\t删除FTP文件："+curr_ftp_path+'/'+fn)
            self.__ftp.delete(curr_ftp_path+'/'+fn)

        for dir in r_dirs:
            ftp_path = curr_ftp_path+'/'+dir
            self.__del_dir(ftp_path)

        print ("\t删除目录："+curr_ftp_path)
        self.__ftp.rmd(curr_ftp_path)

    def sync(self, ftp_root_path, local_root_path=os.getcwd()):
        self.__ftp_root_path = ftp_root_path
        self.__local_root_path = local_root_path 
        self.__sync_ftp_files(self.__ftp_root_path)
        self.__ftp.quit()  # 退出

if __name__ == '__main__':
    FTP_ROOT_PATH = '/0Music'

    sf = SyncFile()
    sf.connect_by_ini("ftp-redmi")
    sf.sync(FTP_ROOT_PATH, "E:\\Core\\Music")
    os.system('pause')